#include "PPU.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

////////////////////////////////////////////////////////////////////////////////////////////////////

// scanlines
#define PRE_RENDER_SCANLINE     -1
#define LAST_VISIBLE_SCANLINE   239
#define POST_RENDER_SCANLINE    240
#define LAST_VBLANK_SCANLINE    260
#define LAST_SCANLINE           261

////////////////////////////////////////////////////////////////////////////////////////////////////

PPU::PPU() {
    m_Bus = 0;
    m_Buffer = 0;
    m_Latch = true;
    m_Reg.R0 = 0;
    m_Reg.R1 = 0;
    m_Reg.R2 = 0;
    m_Reg.R3 = 0;
    m_Reg.R4 = 0;
    m_Reg.R5 = 0;
    m_Reg.R6 = 0;
    m_Reg.R7 = 0;
    m_Scanline = -1;
    m_VBlank = false;
    m_SpriteOverflow = false;
    m_NMI = false;
    m_CanRender = false;
    m_LoopyT = 0;
    m_ScreenData = ( unsigned long* )malloc( 256 * 240 * sizeof(unsigned long) );
    memset( m_ScreenData, 0, 256 * 240 * sizeof(unsigned long) );
    m_CurrentCycle = 0;
    m_EndCycle = 341;
    m_IsShortScanline = false;
    m_RenderPixel = false;
    m_Tile = 0;
    m_PatternTableAddress = 0;
    m_AttributeAddress = 0;
    m_AttributeShift = 0;
    m_AttributeByte = 0;
    m_ColorIndex = ( unsigned char* )malloc( 272 );
    memset( m_ColorIndex, 0, 272 );
    m_RenderAddress = 0;
    m_CurrentColorIndex = 0;
    m_FineX = 0;
    m_PixelCounter = 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

PPU::~PPU() {}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool PPU::WantNMI()const {
    return m_NMI;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::ToggleNMI() {
    m_NMI = false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char PPU::ReadRegister( int reg ) {
    switch( reg ) {
        case 0: {
            return m_Bus;
        } break;

        case 1: {
            return m_Bus;
        } break;

        case 2: {
            return Read2002();
        } break;

        case 3: {
            return m_Bus;
        } break;

        case 4: {
            return Read2004();
        } break;

        case 5: {
            return m_Bus;
        } break;

        case 6: {
            return m_Bus;
        } break;

        case 7: {
            return Read2007();
        } break;

        default: {
            printf( "\nERROR: < %i > no es un registro de PPU valido (lectura)", reg );
            getchar();
            exit( 0 );
        } break;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::WriteRegister( int reg, unsigned char value ) {
    switch( reg ) {
        // TODO
        case 0: {
            m_Bus = value;
            Write2000( value );
        } break;

        case 1: {
            m_Bus = value;
            m_Reg.R1 = value;
        } break;

        case 2: {
            m_Bus = value;
        } break;

        case 3: {
            m_Bus = value;
            Write2003( value );
        } break;

        case 4: {
            m_Bus = value;
            Write2003( value );
        } break;

        case 5: {
            m_Bus = value;
            Write2005( value );
        } break;

        case 6: {
            m_Bus = value;
            Write2006( value );
        } break;

        case 7: {
            m_Bus = value;
            Write2007( value );
        } break;

        default: {
            printf( "\nERROR: < %i > no es un registro de PPU valido (escritura)", reg );
            getchar();
            exit( 0 );
        } break;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char PPU::Read2002() {
    unsigned char t = m_Reg.R2;

    if( m_VBlank ) t |= 0x80;
    if( m_SpriteOverflow ) t |= 0x20;

    m_Reg.R2 &= 0x7f;
    m_Latch = true;

    return t;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char PPU::Read2004() {
    // FIXME: no se si esto es asi o al reves, o si es del todo correcto...
    if( m_VBlank ) {
        return m_Reg.R3;
    } else {
        return m_SRAM[ m_Reg.R3 ];
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char PPU::Read2007() {
    unsigned char t = 0;

    // la direccion corresponde a la paleta
    if( m_Reg.R6 < 0x3f00 ) {
        t = m_Buffer;
        m_Buffer = m_VRAM[ m_Reg.R6 ];
    }

    // la direccion no corresponde a la paleta
    else if( m_Reg.R6 < 0x4000 ) {
        t = m_VRAM[ m_Reg.R6 ];
        m_Buffer = m_VRAM[ (m_Reg.R6 - 0x1000) & 0xffff ];
    }

    m_Reg.R6 += ( TestBit(m_Reg.R0, 2) ? 32 : 1 );

    return t;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2000( unsigned char value ) {
    m_Reg.R0 = value;
    m_LoopyT &= 0x73ff;
    m_LoopyT |= ( (value & 0x03) << 10 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2003( unsigned char value ) {
    m_Reg.R3 = value;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2004( unsigned char value ) {
    // FIXME: puede que el valor de OAMADDR no se modifique si se escribe cuando la PPU esta renderizando
    m_SRAM[ m_Reg.R3++ ] = value;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2005( unsigned char value ) {
    if( m_Latch ) {
        m_LoopyT &= 0x7fe0;
        m_LoopyT |= ( (value & 0xf8) >> 3 );
        m_FineX = ( value & 0x07 );
    } else {
        m_LoopyT &= 0x0c1f;
        m_LoopyT |= ( ((value & 0xf8) << 2) | ((value & 0x07) << 12) );
    }
    m_Latch ^= true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2006( unsigned char value ) {
    if( m_Latch ) {
        m_LoopyT &= 0x40ff;
        m_LoopyT |= ( (value & 0x3f) << 8 );
    } else {
        m_LoopyT &= 0x7f00;
        m_LoopyT |= value;
        m_Reg.R6 = m_LoopyT;
    }
    m_Latch ^= true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2007( unsigned char value ) {
    // name tables
    if( m_Reg.R6 < 0x3000 ) {
        // TODO: mappers
        if( m_Reg.R6 >= 0x2000 ) {
            unsigned short a = ( m_Reg.R6 & 0x2c00 );

            // mirroring vertical
            if( m_Mirroring == VERTICAL ) {
                if( a == 0x2000 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 + 0x0800) & 0xffff ] = value;
                } else if( a == 0x2400 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 + 0x0800) & 0xffff ] = value;
                } else if( a == 0x2800 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 - 0x0800) & 0xffff ] = value;
                } else if( a == 0x2c00 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 - 0x0800) & 0xffff ] = value;
                }
            }

            // mirroring horizontal
            else if( m_Mirroring == HORIZONTAL ) {
                if( a == 0x2000 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 + 0x0400) & 0xffff ] = value;
                } else if( a == 0x2400 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 - 0x0400) & 0xffff ] = value;
                } else if( a == 0x2800 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 + 0x0400) & 0xffff ] = value;
                } else if( a == 0x2c00 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 - 0x0400) & 0xffff ] = value;
                }
            }
        }
    }

    
    // paleta
    else if( m_Reg.R6 < 0x4000 ) {
        value &= 0x3f;
        m_VRAM[ m_Reg.R6 ] = value;
        if( (m_Reg.R6 == 0x3f00) || (m_Reg.R6 == 0x3f10) ) {
            m_VRAM[ 0x3f00 ] = value;
            m_VRAM[ 0x3f10 ] = value;
        } else if( (m_Reg.R6 == 0x3f04) || (m_Reg.R6 == 0x3f14) ) {
            m_VRAM[ 0x3f04 ] = value;
            m_VRAM[ 0x3f14 ] = value;
        } else if( (m_Reg.R6 == 0x3f08) || (m_Reg.R6 == 0x3f18) ) {
            m_VRAM[ 0x3f08 ] = value;
            m_VRAM[ 0x3f18 ] = value;
        } else if( (m_Reg.R6 == 0x3f0c) || (m_Reg.R6 == 0x3f1c) ) {
            m_VRAM[ 0x3f0c ] = value;
            m_VRAM[ 0x3f1c ] = value;
        }
    }

    m_Reg.R6 += ( TestBit(m_Reg.R0, 2) ? 32 : 1 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::WriteSpriteDMA( std::list<unsigned char> data ) {
    unsigned char address = 0;
    for( std::list<unsigned char>::iterator value = data.begin(); value != data.end(); value++ ) m_SRAM[ address++ ] = ( *value );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::RunScanline() {
    if( m_Scanline <= PRE_RENDER_SCANLINE ) {
        // (al menos por lo pronto) nada...
        // en el primer scanline, si el background o los sprites estan habilitados, LoopyT se copia a PPU[6]
        if( TestBit(m_Reg.R1, 3) || TestBit(m_Reg.R1, 4) ) m_Reg.R6 = m_LoopyT;
    } else if( m_Scanline <= LAST_VISIBLE_SCANLINE ) {
        // al principio de cada scanline visible, si el background o los sprites estan habilitados, LoopyT se copia a PPU[6] y se resetean los
        // bits de scrolling relativos al eje X (los 5 mas bajos de de PPU[6]) y el bit asociado a la Name Table (el bit 10 de PPU[6])

        if( TestBit(m_Reg.R1, 3) || TestBit(m_Reg.R1, 4) ) {
            // FIXME: que onda con esto??? 'nes_emu' primero dice que hay que se hace al principio de cada scanline, y poco mas adelante dice que
            // se hace al final de cada una...
            m_Reg.R6 &= 0xfbe0;
            m_Reg.R6 |= ( m_LoopyT & 0x041f );

            // dibujar en el buffer de salida el background y los sprites
            DrawSprites( false );
            DrawBackground();
            DrawSprites( true );

            // FIXME: no me termina de quedar del todo claro, pero esto es mas o menos lo que entendi...
            // al final de cada scanline visible se incremente el scrolling en el eje Y (los bits 12, 13 y 14 de PPU[6]). Cuando estos bits sean
            // sinmultaneamente 1 se habran renderizado 8 scanlines (111b = 7d, o sea 8 scanlines). En este punto, se desea saber si las 240 scanlines
            // visibles ya fueron renderizadas. Para ello, se chequea lo que se denomina 'y-offset', que corresponde a los bits 5, 6, 7, 8 y 9 de PPU[6]; si
            // estos son sinmultaneamente 1, entonces se habran renderizado los 8 scanlines de 32 posibles tiles (11111b = 31d, o sea 32 tiles). En terminos normales,
            // verticalmente solo entran 30 tiles, pero puede que eventualmente se forzarsen 32 tiles mediante programacion.
            // Una vez que el y-offset llega a 29 (o sea los 30 tiles verticales), entonces se reseta el scrolling en Y y se alterna el bit asociado a la
            // Name Table. Por otro lado, si el valor llegase eventualmente a 31, entonces se resea el scrolling en Y *PERO* sin alternar el bit asociado a la Name Table.

            if( (m_Reg.R6 & 0x7000) == 0x7000 ) { // cada 8 scanlines...
                unsigned short t = ( m_Reg.R6 & 0x03e0 ); // valor del y-offset
                m_Reg.R6 &= 0x0fff; // resetear el scrolling en Y

                switch( t ) { // chequear el y-offset
                    case 0x03a0: { // si es 28...
                        m_Reg.R6 ^= 0x0ba0;
                    } break;

                    case 0x03e0: { // si es 30...
                        m_Reg.R6 ^= 0x03e0;
                    } break;

                    default: { // si no estamos ante ninguno de los dos casos anteriores, aumentamos el scrolling en Y normalmente
                        m_Reg.R6 += 0x0020;
                    } break;
                }
            } else { // si aun no se llego a los 8 scanlines, se incrementa el Fine-Y
                m_Reg.R6 += 0x1000;
            }
        }

        if( m_Scanline == LAST_VISIBLE_SCANLINE ) m_CanRender = true;
    } else if( m_Scanline <= POST_RENDER_SCANLINE ) {
        // el VBlank comienza al final de este scanline
        m_CanRender = false;
        m_VBlank = true;
        if( TestBit(m_Reg.R0, 7) ) RaiseInterrupt( NMI );
    } else if( m_Scanline <= LAST_VBLANK_SCANLINE ) {
        // en VBlank, PPU descansando...
    } else if( m_Scanline <= LAST_SCANLINE ) {
        // fin del VBlank
        m_VBlank = false;
        m_Scanline = -2; // es -2, y no -1, porque estamos contando los scanlines desde -1 (en lugar de hacerlo desde 0)
    }

    m_Scanline++;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::DrawBackground() {
    for( int tile = 0; tile < 32; tile++ ) {
        unsigned short tile_address = ( 0x2000 | (m_Reg.R6 & 0x0fff) );
        unsigned short pt_address = ( (m_VRAM[tile_address] << 4) | (m_Reg.R6 >> 12) | ((TestBit(m_Reg.R0, 4) ? 1 : 0) << 12) );
        unsigned char at_byte =  m_VRAM[ 0x23c0 | (m_Reg.R6 & 0x0c00) | ATTRIBUTE_LOCK[m_Reg.R6 & 0x03ff] ];
        at_byte &= ATTRIBUTE_MASK[ m_Reg.R6 & 0x03ff ];
        at_byte >>= ATTRIBUTE_SHIFT[ m_Reg.R6 & 0x03ff ];
        for( int bit = 7; bit >= 0; bit-- ) {
            unsigned char index = ( (at_byte << 2) | (((m_VRAM[pt_address | 8] >> bit) & 0x01) << 1) | ((m_VRAM[pt_address] >> bit) & 0x01) );
            m_ScreenData[ m_Scanline * 256 + tile * 8 + (bit ^ 7) ] = PALETTE[ m_VRAM[0x3f00 | index] ];
        }

        // al final de cada tile renderizado, se incrementa el scrolling en el eje X. Cuando el valor del scrolling en X llegue a 0x1f (31d, lo que implica
        // 32 tiles) se habra renderizado un scanline completo. Entonces, se reseta el scrolling en X y se alterna el bit asociado a la Name Table.
        if( (m_Reg.R6 & 0x001f) == 0x001f ) m_Reg.R6 ^= 0x041f;
        else                                m_Reg.R6++;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::DrawSprites( bool priority ) {
    int counter = 0;
    m_SpriteOverflow = false;
    for( int i = 252; i >= 0; i -= 4 ) {
        unsigned char b0 = m_SRAM[ i ]; // coordenada Y
        unsigned char b1 = m_SRAM[ (i + 1) & 0xff ];
        unsigned char b2 = m_SRAM[ (i + 2) & 0xff ];
        unsigned char b3 = m_SRAM[ (i + 3) & 0xff ]; // coordenada X
    
        unsigned char range = ( (m_Scanline - 21 - b0 + (TestBit(m_Reg.R0, 5) ? 16 : 8)) & 0xff );
        bool in_range = ( (range >= 0) && (range <= 7) );
        
        // 0 => delante del background; 1 => detras del background
        if( in_range && (TestBit(b2, 5) == priority) ) {
            if( ++counter > 8 ) m_SpriteOverflow = true;
            // FIXME: por el momento solo implemento los sprites de 8x8; corregir al implementar sprites de 8x16.
            if( ! TestBit(m_Reg.R0, 5) ) {
                unsigned char line = ( m_Scanline % 8 );

                // flip vertical => TestBit( b2, 7 )
                unsigned char pt_entry_l = m_VRAM[ (TestBit(m_Reg.R0, 3) ? 0x1000 : 0x0000) | ((b1 << 4) & 0xffff) | (TestBit(b2, 7) ? 7 - line : line) ];
                unsigned char pt_entry_h = m_VRAM[ (TestBit(m_Reg.R0, 3) ? 0x1000 : 0x0000) | ((b1 << 4) & 0xffff) | (TestBit(b2, 7) ? 7 - line : line) | 8 ];

                if( TestBit(b2, 6) ) { // flip horizontal
                    for( int k = 0; k < 8; k++ ) {
                        unsigned char index = ( ((((pt_entry_h >> k) & 0x01) << 1) | ((pt_entry_l >> k) & 0x01)) & 0xff );
                        if( index ) {
                            index = ( (((b2 & 0x03) << 2) | index ) & 0xff );
                            if( (b0 + line + 1) * 256 + b3 < 256 * 240 ) m_ScreenData[ (b0 + line + 1) * 256 + b3 ] = PALETTE[ m_VRAM[0x3f10 | index] ];
                        }
                        b3++;
                    }
                } else {
                    for( int k = 7; k >= 0; k-- ) {
                        unsigned char index = ( ((((pt_entry_h >> k) & 0x01) << 1) | ((pt_entry_l >> k) & 0x01)) & 0xff );
                        if( index ) {
                            index = ( (((b2 & 0x03) << 2) | index ) & 0xff );
                            if( (b0 + line + 1) * 256 + b3 < 256 * 240 ) m_ScreenData[ (b0 + line + 1) * 256 + b3 ] = PALETTE[ m_VRAM[0x3f10 | index] ];
                        }
                        b3++;
                    }
                }
            }
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::RaiseInterrupt( Interrupt i ) {
    if( i == NMI ) m_NMI = true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool PPU::WantRender() {
    return m_CanRender;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Run( int cycles ) {
    bool is_rendering = ( TestBit(m_Reg.R1, 3) || TestBit(m_Reg.R1, 4) );

    for( int c = 0; c != cycles; ++c ) {
        m_CurrentCycle++;

        if( m_CurrentCycle == 256 ) {
            if( m_Scanline > PRE_RENDER_SCANLINE ) m_EndCycle = 341;
            else if( m_IsShortScanline && is_rendering ) m_EndCycle = 340;
        }
        else if( m_CurrentCycle == 304 ) {
            if( (m_Scanline == PRE_RENDER_SCANLINE) && is_rendering ) m_Reg.R6 = m_LoopyT;
        }
        else if( m_CurrentCycle == m_EndCycle ) {
            m_PixelCounter = 0;
            m_Scanline++;
            m_CurrentCycle = 0;
            if( m_Scanline == 0 ) m_RenderPixel = true;
            else if( m_Scanline == POST_RENDER_SCANLINE ) m_RenderPixel = false;
            else if( m_Scanline == 241 ) {
                m_CanRender = true;
                m_VBlank = true;
                m_Reg.R3 = 0;
                if( TestBit(m_Reg.R0, 7) ) RaiseInterrupt( NMI );
            } else if( m_Scanline == LAST_SCANLINE ) {
                m_Scanline = -1;
                is_rendering = ( TestBit(m_Reg.R1, 3) || TestBit(m_Reg.R1, 4) );
                m_IsShortScanline ^= true;
            }
        }
        else if( (m_Scanline == PRE_RENDER_SCANLINE) && (m_CurrentCycle == 1) ) m_VBlank = false;

        if( is_rendering ) {
            switch( m_CurrentCycle ) {
                case   0: case   8: case  16: case  24: case  32: case  40: case  48: case  56: case  64: case  72: case  80: case  88: case  96:
                case 104: case 112: case 120: case 128: case 136: case 144: case 152: case 160: case 168: case 176: case 184: case 192: case 200:
                case 208: case 216: case 224: case 232: case 240: case 248: case 320: case 328: {
                    m_Tile = m_VRAM[ 0x2000 | (m_Reg.R6 & 0x0fff) ];
                } break;

                case   1: case   9: case  17: case  25: case  33: case  41: case  49: case  57: case  65: case  73: case  81: case  89: case  97:
                case 105: case 113: case 121: case 129: case 137: case 145: case 153: case 161: case 169: case 177: case 185: case 193: case 201:
                case 209: case 217: case 225: case 233: case 241: case 249: case 321: case 329: {
                    m_PatternTableAddress = ( (m_Tile << 4) | (m_Reg.R6 >> 12) | ((TestBit(m_Reg.R0, 4) ? 1 : 0) << 12) );
                } break;

                case   2: case  10: case  18: case  26: case  34: case  42: case  50: case  58: case  66: case  74: case  82: case  90: case  98:
                case 106: case 114: case 122: case 130: case 138: case 146: case 154: case 162: case 170: case 178: case 186: case 194: case 202:
                case 210: case 218: case 226: case 234: case 242: case 250: {
                    m_AttributeAddress = ( 0x23c0 | (m_Reg.R6 & 0x0c00) | ATTRIBUTE_LOCK[m_Reg.R6 & 0x03ff] );
                    m_AttributeShift = ATTRIBUTE_SHIFT[ m_Reg.R6 & 0x03ff ];
                    m_RenderAddress = m_AttributeAddress;
                } break;

                case   3: case  11: case  19: case  27: case  35: case  43: case  51: case  59: case  67: case  75: case  83: case  91: case  99:
                case 107: case 115: case 123: case 131: case 139: case 147: case 155: case 163: case 171: case 179: case 187: case 195: case 203:
                case 211: case 219: case 227: case 235: case 243: case 323: case 331: {
                    m_RenderAddress = m_AttributeAddress;
                    m_AttributeByte = m_VRAM[ m_AttributeAddress ];
                    m_AttributeByte &= ATTRIBUTE_MASK[ m_Reg.R6 & 0x03ff ];
                    m_AttributeByte >>= ATTRIBUTE_SHIFT[ m_Reg.R6 & 0x03ff ];
                    unsigned char color = ( (m_AttributeByte & 0x03) << 2 );
                    int index = ( ((m_CurrentCycle == 323) || (m_CurrentCycle == 331)) ? m_CurrentCycle - 323 : m_CurrentCycle + 13 );
                    for( int k = 0; k != 8; ++k ) m_ColorIndex[ index + k ] = color;
                    if( (m_Reg.R6 & 0x001f) == 0x001f ) m_Reg.R6 ^= 0x041f;
                    else                                m_Reg.R6++;
                } break;

                case   4: case  12: case  20: case  28: case  36: case  44: case  52: case  60: case  68: case  76: case  84: case  92: case 100:
                case 108: case 116: case 124: case 132: case 140: case 148: case 156: case 164: case 172: case 180: case 188: case 196: case 204:
                case 212: case 220: case 228: case 236: case 244: case 252: case 324: case 332: {
                    m_RenderAddress = m_PatternTableAddress;
                } break;

                case   5: case  13: case  21: case  29: case  37: case  45: case  53: case  61: case  69: case  77: case  85: case  93: case 101:
                case 109: case 117: case 125: case 133: case 141: case 149: case 157: case 165: case 173: case 181: case 189: case 197: case 205:
                case 213: case 221: case 229: case 237: case 245: case 253: case 325: case 333: {
                    int index = ( ((m_CurrentCycle == 325) || (m_CurrentCycle == 333)) ? m_CurrentCycle - 325 : m_CurrentCycle + 11 );
                    for( int k = 0; k != 8; ++k ) m_ColorIndex[ index + k ] |= ( (m_VRAM[m_RenderAddress] >> (k ^ 7)) & 0x01 );
                } break;

                case   6: case  14: case  22: case  30: case  38: case  46: case  54: case  62: case  70: case  78: case  86: case  94: case 102:
                case 110: case 118: case 126: case 134: case 142: case 150: case 158: case 166: case 174: case 182: case 190: case 198: case 206:
                case 214: case 222: case 230: case 238: case 246: case 254: case 326:case 334: {
                    m_RenderAddress = ( m_PatternTableAddress | 8 );
                } break;

                case   7: case  15: case  23: case  31: case  39: case  47: case  55: case  63: case  71: case  79: case  87: case  95: case 103:
                case 111: case 119: case 127: case 135: case 143: case 151: case 159: case 167: case 175: case 183: case 191: case 199: case 207:
                case 215: case 223: case 231: case 239: case 247: case 255: case 327: case 335: {
                    int index = ( ((m_CurrentCycle == 327) || (m_CurrentCycle == 335)) ? m_CurrentCycle - 327 : m_CurrentCycle + 9 );
                    for( int k = 0; k != 8; ++k ) m_ColorIndex[ index + k ] |= ( ((m_VRAM[m_RenderAddress] >> (k ^ 7)) & 0x01) << 1 );
                } break;

                case 251: {
                    m_RenderAddress = m_AttributeAddress;
                    m_AttributeByte = m_VRAM[ m_AttributeAddress ];
                    m_AttributeByte &= ATTRIBUTE_MASK[ m_Reg.R6 & 0x03ff ];
                    m_AttributeByte >>= ATTRIBUTE_SHIFT[ m_Reg.R6 & 0x03ff ];
                    unsigned char color = ( (m_AttributeByte & 0x03) << 2 );
                    for( int k = 0; k != 8; ++k ) m_ColorIndex[ m_CurrentCycle + 13 + k ] = color;
                    if( (m_Reg.R6 & 0x001f) == 0x001f ) m_Reg.R6 ^= 0x041f;
                    else                                m_Reg.R6++;
                    if( (m_Reg.R6 & 0x7000) == 0x7000 ) {
                        unsigned short t = ( m_Reg.R6 & 0x03e0 );
                        m_Reg.R6 &= 0x0fff;
                        switch( t ) {
                            case 0x03a0: m_Reg.R6 ^= 0x0ba0; break;
                            case 0x03e0: m_Reg.R6 ^= 0x03e0; break;
                            default: m_Reg.R6 += 0x0020; break;
                        }
                    } else {
                        m_Reg.R6 += 0x1000;
                    }
                } break;

                case 256: case 258: case 264: case 266: case 272: case 274: case 280: case 282: case 288: case 290: case 296: case 298: case 304:
                case 306: case 312: case 314: {
                    m_RenderAddress = ( 0x2000 | (m_Reg.R6 & 0x0fff) );
                } break;

                case 257: {
                    m_Reg.R6 &= 0xfbe0;
                    m_Reg.R6 |= ( m_LoopyT & 0x041f );
                    m_RenderAddress = ( 0x2000 | (m_Reg.R6 & 0x0fff) );
                } break;

                case 265: case 273: case 281: case 289: case 297: case 305: case 313: {
                    // nada!
                } break;

                case 259: case 267: case 275: case 283: case 291: case 299: case 307: case 315: {
                    // TODO: sprites
                } break;

                case 260: case 268: case 276: case 284: case 292: case 300: case 308: case 316: {
                    m_RenderAddress = m_PatternTableAddress;
                } break;

                case 261: case 269: case 277: case 285: case 293: case 301: case 309: case 317: {
                    // TODO: sprites
                } break;

                case 262: case 270: case 278: case 286: case 294: case 302: case 310: case 318: {
                    m_RenderAddress = ( m_PatternTableAddress | 8 );
                } break;

                case 263: case 271: case 279: case 287: case 295: case 303: case 311: case 319: {
                    // TODO: sprites
                } break;

                case 336: case 338: {
                    m_RenderAddress = ( 0x2000 | (m_Reg.R6 & 0x0fff) );
                } break;

                case 337: case 339: case 340: {
                    // nada!
                } break;
            }

            if( m_RenderPixel && (m_CurrentCycle < 256) ) {
                if( TestBit(m_Reg.R1, 3) && ((m_CurrentCycle >= 8) || TestBit(m_Reg.R1, 1)) ) m_CurrentColorIndex = m_ColorIndex[ m_CurrentCycle + m_FineX ];
                else                                                                          m_CurrentColorIndex = 0;
                // TODO: sprites
                if( ! is_rendering ) {
                    if( (m_Reg.R6 & 0x3f00) == 0x3f00 ) m_CurrentColorIndex = ( m_Reg.R6 & 0x1f );
                    m_ScreenData[ m_PixelCounter ] = PALETTE[ m_CurrentColorIndex ];
                }
            }
        }
    }

    m_PixelCounter += cycles;
}

////////////////////////////////////////////////////////////////////////////////////////////////////